studiocms
Version:
Astro Native CMS for AstroDB. Built from the ground up by the Astro community.
100 lines (91 loc) • 4.04 kB
text/typescript
import { type OAuthProviders, oAuthProviders } from 'studiocms:plugins/auth/providers';
import type { APIContext, APIRoute } from 'astro';
import { Effect, genLogger } from '../../../../../effect.js';
/**
* Creates a standardized HTTP response for authentication provider errors.
*
* @param error - The error message to include in the response body.
* @param status - The HTTP status code for the response.
* @returns A `Response` object with a JSON body containing the error message and the specified status code.
*/
export const ProviderResponse = (
error: string,
status: number
): Effect.Effect<Response, never, never> =>
Effect.succeed(new Response(JSON.stringify({ error }), { status }));
/**
* Executes the provided API route handler function with the given context and returns its resulting Promise.
*
* @param fn - The API route handler function to execute.
* @param context - The context object to pass to the handler function.
* @returns A Promise that resolves to a Response object returned by the handler.
*/
async function promisifyFn(fn: APIRoute, context: APIContext): Promise<Response> {
return await fn(context);
}
/**
* Wraps an API route handler function in an Effect, converting it into a promise-based effectful computation.
*
* @param fn - The API route handler function to be executed.
* @param context - The API context to be passed to the handler.
* @returns An Effect that resolves with the result of the handler or rejects with an error if execution fails.
*
* @remarks
* If the handler throws an error, it is caught and wrapped in a new `Error` with a descriptive message.
*/
const promisify = Effect.fn(function* (fn: APIRoute, context: APIContext) {
return yield* Effect.tryPromise({
try: () => promisifyFn(fn, context),
catch: (error) => new Error(`Failed to execute API route: ${error}`),
});
});
/**
* Dispatches an authentication request to the appropriate OAuth provider handler.
*
* This generator function locates the specified provider by its `safeName` from the provided list,
* checks if the provider is enabled, and then invokes the appropriate handler (`initSession` or `initCallback`)
* for the authentication flow. If the provider or handler is not found, or if the provider is not enabled,
* it yields an appropriate error response.
*
* @param context - The API context containing request parameters and other relevant data.
* @param handler - The handler to invoke, either `'initSession'` or `'initCallback'`.
* @param providers - An array of provider configurations, each containing a `safeName`, `enabled` flag,
* and optional `initSession` and `initCallback` route handlers.
* @returns A generator yielding the result of the provider handler or an error response.
*/
const dispatchToAuthProvider = (
context: APIContext,
handler: 'initSession' | 'initCallback',
providers: OAuthProviders
) =>
genLogger('OAuthAPIEffect.dispatchToAuthProvider')(function* () {
const provider = context.params.provider;
const matchedProvider = providers.find((p) => p.safeName === provider);
if (!matchedProvider) {
return yield* ProviderResponse('Provider not found', 404);
}
if (!matchedProvider.enabled) {
return yield* ProviderResponse('Provider is not configured', 403);
}
const handlerFn = matchedProvider[handler];
if (!handlerFn) {
return yield* ProviderResponse('Provider handler not found', 501);
}
return yield* promisify(handlerFn, context);
});
export class OAuthAPIEffect extends Effect.Service<OAuthAPIEffect>()('OAuthAPIEffect', {
effect: genLogger('studiocms/routes/api/auth/[provider]/_shared')(function* () {
return {
initSession: (context: APIContext) =>
dispatchToAuthProvider(context, 'initSession', oAuthProviders),
initCallback: (context: APIContext) =>
dispatchToAuthProvider(context, 'initCallback', oAuthProviders),
};
}),
}) {
// Export Dependency Providers
/**
* Main Dependencies Provider
*/
static Provide = Effect.provide(OAuthAPIEffect.Default);
}